/**
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var prefs_obj = new gadgets.Prefs();
var searchURL_tmpl = "http://code.google.com/p/%PROJECT%/issues/list?q=%SEARCH%";
var title_tmpl = "%PROJECT% issues";
var urlMaster_tmpl = 'http://code.google.com/p/%PROJECT%/issues/list?sort=%SORT%&colspec=%COLSPEC%&num=%NUM%&start=%START%';
var invalidProject_tmpl = "The project name, %PROJECT%, in the user preferences is invalid.";
var url_tmpl = "";
var projectName_str = "";
var sortBy_str = prefs_obj.getString("sortBy") || "-Opened";
var colspec_tmpl = "ID+%SORTBY%+Summary";
var colspec_str = sortBy_str.search("-") == -1 ? colspec_tmpl.replace("%SORTBY%", sortBy_str) : colspec_tmpl.replace("%SORTBY%", sortBy_str.substring(1, sortBy_str.length));
var issueType_str = prefs_obj.getString("issueType");
var issueData_arr = new Array();
var totalItems_str = "";


// The user name is used by the filtering functionality when calling the URL to scrape.
var userName_str = prefs_obj.getString("userName");

// If the issueType requires a user name, and there isn't one, then
// fail back to 'All issues' quietly.
if (userName_str == ""  && (issueType_str == "assignedToMe" || issueType_str == "reportedByMe")) {
  issueType_str = "all";
}

var filterSearch_bool = false;
var filterSearch_str = "";
setFilteredSearch();

// We could potentially save start to a UserPref to save the current page but would
// that actually be useful?  Wouldn't it always be better to start on the first page?
// Our global to track the current page based on the starting entry.
var start_num = 0;      // For a single project, more efficient
var currentPage_num = 1;  // For multiple projects

// Multiple project tracking
var retrievedProjects_num = 0;
var otherProjects_str = _esc(prefs_obj.getString("otherProjects").toLowerCase());
var otherProjects_arr = otherProjects_str.split("%7C");
var singleProject_bool = otherProjects_str == "";
var totalProjects_num = otherProjects_arr.length;

/**
 * We need to build our URL before passing to the common init as it requires UserPrefs
 * and tracking vars <code>sortBy<code>, <code>colspec</code>, and <code>start_num</code>.
 */
function localInit() {
  // Set our callback counter the beginning
  retrievedProjects_num = 0;

  // We need the url_tmpl right away for init
  rebuildURL();

  // Call the common init
  init();
}

/**
 * Rebuild the URL for retrieval based on current UserPrefs and tracking vars.
 */
function rebuildURL() {
  // Construct the url template (it's just missing the project name)
  url_tmpl = urlMaster_tmpl.replace("%SORT%", sortBy_str).replace("%COLSPEC%", colspec_str);
  if (singleProject_bool) {
    url_tmpl = url_tmpl.replace("%NUM%", 10).replace("%START%", start_num);
  } else {
    url_tmpl = url_tmpl.replace("%NUM%", 41).replace("%START%", 0);
  }

  if (filterSearch_bool) {
    url_tmpl += filterSearch_str;
  }
}

/**
 * Actually render the content of our gadget.
 *
 * @param resposne_str  The page to scrape.
 */
function render(response_str) {
  // Reset issue data holder
  issueData_arr = new Array();

  // Fetch the other projects
  if (!singleProject_bool) {
    for(var i = 0; i < totalProjects_num; i++) {
      var url = url_tmpl.replace("%PROJECT%", otherProjects_arr[i]);
      fetchAsStringLegacy(url, responseHandler);
    }
  }

  // Handle the primary project
  responseHandler(response_str, true);
}

/**
 * Receives a page, builds a <code>jQuery</code> object and sends it to the scraper.
 *
 * @param response_str        The page to be scraped.
 * @param primaryProject_bool Indicates whether this is the primary project
 */
function responseHandler(response_str, primaryProject_bool) {
  // This would generally be run through the common init, but the secondary projects
  // are being fetched by code in this file.  Still, we can use the RegEx from
  // codegadgets.js as it is a global.

  // Check to see if it's invalid
  if (response_str.search(invalid_re) != -1) {
    // Invalid Project

    // Print a mini-message here
    var invalid_msg = new gadgets.MiniMessage(moduleID_num);
    invalid_msg.createTimerMessage(invalidProject_tmpl.replace("%PROJECT%", scrapeInvalidProjectName(response_str)), 5);

    totalProjects_num--;
    return;
  }

  // If this is the primary project, grab the total number of items.
  // This will eventually be passed to the common paging function for
  // the "See all XXX items" link
  if (primaryProject_bool) {
    var pagination_str = jQuery(".pagination:first", jQuery(response_str)).html();
    totalItems_str = pagination_str == null ? "0" : new String(pagination_str.match(/.*of.*/)).replace(/.*of\ /, "");
  }

  if (response_str.search(/Your search did not generate any results./) == -1) {
    // Construct a jQuery object for the results table in our retrieved text
    var response_obj = jQuery("#resultstable", jQuery(response_str));

    // Build the data table
    scrapeContent(response_obj, scrapeProjectName(response_str));
  }

  if (singleProject_bool) {
    inject(response_str);
  } else if (++retrievedProjects_num == (totalProjects_num + 1)) {
    inject("");
  }
}

/**
 * Get the project name from a response page.
 *
 * @param response_str  The retrieved page.
 * @returns             The project name as a <code>string</code>.
 */
function scrapeProjectName(response_str) {
  var title_re = /<title>\s*[^\s]+\s*-\s*([^\s]+)/im;
  var match = response_str.match(title_re);
  if (match.length == 2) {
    return match[1];
  } else {
    return "";
  }
}

/**
 * Get the project name from a 'not found' response page.
 *
 * @param response_str  The retrieved page.
 * @returns             The project name as a <code>string</code>.
 */
function scrapeInvalidProjectName(response_str) {
  // /p/XXX/issues/
  var startIndex_num = response_str.search("/p/");
  var endIndex_num = response_str.search("/issues/");

  // If it doesn't exist, die gracefully
  if (startIndex_num == -1 || endIndex_num == -1) {
    return "";
  }

  // Offset the start from our deterministic search string
  startIndex_num += 3;

  return response_str.substring(startIndex_num, endIndex_num);
}

/**
 * Inject the content HTML to the gadget's frame.
 *
 * @param response_str  If in single project mode, this will be the retrieved project
 *                      page for paging.
 */
function inject(response_str) {
  var typesForm_str = "<form><select id='selectType'><option value='all'>All Issues</option>";
  // "to me" options are only available on iGoogle
  if (_args()["synd"] == "ig") {
    typesForm_str += "<option value='assignedToMe'>Issues Assigned to Me</option>";
    typesForm_str += "<option value='reportedByMe'>Issues Reported by Me</option>";
  }
  typesForm_str += "<option value='starred'>Important Issues</option></select></form>";

  // Final data build
  var output_str = "";

  // Add the filter select box
  output_str += typesForm_str;

  // Add the table data
  output_str += buildContent();

  // Generate Paging info, add a break even if no paging
  if (singleProject_bool) {
    var paging_str = generatePagingFromPage(response_str, start_num);
    output_str += "<br />" + paging_str;
  } else {
    var totalPages_num = Math.ceil(issueData_arr.length / 10);      // Get the number of pages
    totalPages_num = totalPages_num > 4 ? 4 : totalPages_num;       // Restrict pages to 4;
    var extraPages_bool = issueData_arr.length > 40;
    if (totalPages_num > 0) {
      output_str += "<br />" + generatePaging(totalPages_num, currentPage_num, extraPages_bool, totalItems_str);
    }
  }

  // Inject the data
  $("#content_div").html(output_str);

  // Add event handlers for paging
  if (singleProject_bool) {
    addPagingEvents(pagingCallbackSingle);
  } else {
    addPagingEvents(pagingCallbackMultiple);
  }

  // Add an event handler for the select box that should reload the content with a different filter
  addSelectEvent();

  // Auto-adjust the gadget height
  adjustIFrameHeight();
}

/**
 * Scrapes the content out of the specified <code>response_obj</code>.
 *
 * @param response_obj            The <code>jQuery</code> object that is limited to the
 *                                <code>#resultstable</code> table.
 * @param currentProjectName_str  The project name of the data we're scraping through.
 */
function scrapeContent(response_obj, currentProjectName_str) {
  var id_str = "";
  var sort_str = "";
  var summary_str = "";

  response_obj.find("tr").each(function(i) {
    jQuery(this).find("td:not(:last):not(:first)").each(function(i) {
      switch(i) {
        case 0:
          // The first cell is the id and should be a link
          id_str = jQuery(this).text();
          break;
        case 1:
          // This is the modified date by default, but is UserPref mutable.
          sort_str = jQuery(this).text();
          break;
        case 2:
          // This is a strange, nearly empty cell that googlecode inserts into the table
          // It contains '\n&nbsp;'.  We don't need it
          break;
        default:
          // This is your average, regular, everyday cell
          summary_str = jQuery(this).text();
          break;
      }
    });

    // Make sure to get rid of empty rows
    if (id_str != "" && summary_str != "") {
      issueData_arr[issueData_arr.length++] = new issueData(sort_str, id_str, summary_str, currentProjectName_str);
    }
  });
}

/**
 * Called by render to build the data table HTML from <code>issueData_arr</code>.
 *
 * @returns The content page as a <code>string</code>.
 */
function buildContent() {
  var linkCell_tmpl = "<td valign='top'><a href='http://code.google.com/p/%PROJECT%/issues/detail?id=%ID%' target='_blank'>%ID%</a>%CONTENT%</td>";
  var nowrapCell_tmpl = "<td valign='top' style='white-space: nowrap; border-top: 1px solid #BBBBBB;'>%CONTENT%</td>";
  var regularCell_tmpl = "<td valign='top'>%CONTENT%</td>";
  var linkCellSingle_tmpl = "<td valign='top' style='border-top: 1px solid #BBBBBB;'><a href='http://code.google.com/p/%PROJECT%/issues/detail?id=%ID%' target='_blank'>%ID%</a>%CONTENT%</td>";
  var regularCellSingle_tmpl = "<td valign='top'style='border-top: 1px solid #BBBBBB;'>%CONTENT%</td>";
  var result_str = "";
  var i;

  // If there are no issues, return a nice string for the user
  if (issueData_arr.length == 0) {
    return "There are no issues matching this query.";
  }

  // Sort the data first
  if (sortBy_str == "-Opened" || sortBy_str == "-Modified" || sortBy_str == "-Closed") {
    issueData_arr.sort(sortIssueDataByDate);
  } else {
    issueData_arr.sort(sortIssueData);
  }

  // Iterate the rows of our array to build our data table
  result_str += "<center><table cellspacing='0' cellpadding='0' width='100%' id='#resultstable' style='border-bottom: 1px solid #BBBBBB;'>";

  // Assume single project at itor var creation
  var start_num = 0;
  var end_num = issueData_arr.length;

  // Modify itor vars if more than one project
  if (!singleProject_bool) {
    start_num = (currentPage_num * 10) - 10;
    end_num = currentPage_num * 10;
    end_num = end_num <= issueData_arr.length ? end_num : issueData_arr.length;
  }

  for (i = start_num; i < end_num; i++) {
    result_str += "<tr>";

    if (!singleProject_bool) {
      // Multiple projects have two rows per entry.  The first row is of the form "Time | Project Name"
      result_str += nowrapCell_tmpl.replace("%CONTENT%", issueData_arr[i].sort_str.replace(/\ *ago/, ""));
      result_str += nowrapCell_tmpl.replace("%CONTENT%", issueData_arr[i].projectName_str);

      // New row
      result_str += "</tr><tr>";

      // "ID(link) | Summary"
      result_str += linkCell_tmpl.replace("%PROJECT%", issueData_arr[i].projectName_str)
                                 .replace("%ID%", issueData_arr[i].id_str)
                                 .replace("%ID%", issueData_arr[i].id_str)
                                 .replace("%CONTENT%", "");

      // Get rid of trailing white-space and ellipsify words over 25 characters
      result_str += regularCell_tmpl.replace("%CONTENT%", ellipsifyWords(issueData_arr[i].summary_str.replace(/[\n\r\ ]*$/, ""), 20));
    } else {
      // A single project will replace where the project name goes with and empty string literal
      result_str += linkCellSingle_tmpl.replace("%PROJECT%", issueData_arr[i].projectName_str)
                                       .replace("%ID%", issueData_arr[i].id_str)
                                       .replace("%ID%", issueData_arr[i].id_str)
                                       .replace("%CONTENT%", "");

      // Also, we will keep unbreakble whitespace for the date as we'll have a bit extra room
      result_str += nowrapCell_tmpl.replace("%CONTENT%", issueData_arr[i].sort_str.replace(/\ *ago/, ""));

      // Get rid of trailing white-space and ellipsify words over 25 characters
      result_str += regularCellSingle_tmpl.replace("%CONTENT%", ellipsifyWords(issueData_arr[i].summary_str.replace(/[\n\r\ ]*$/, ""), 20));
    }


    result_str += "</tr>";
  }
  result_str += "</table></center>";

  return result_str;
}

/**
 * Event handler for paging links when there is only a single project in play.
 * @param page_num  The page number or global flag for <code>PAGE_NEXT</code>,
 *                  <code>PAGE_NEXT</code>, or <code>PAGE_MORE</code>.
 */
function pagingCallbackSingle(page_num) {
  if (page_num == PAGE_MORE) {
    moreURL_str = urlMaster_tmpl.replace("%SORT%", sortBy_str) // Set SORT from user pref
                                .replace("&colspec=%COLSPEC%", "")                // Remove COLSPEC - new page will have google defaults
                                .replace("%START%", 40)                           // We'll start at 40.  40 must exist or the more link wouldn't
                                .replace("&num=%NUM%", "")                          // Don't limit the listing
                                .replace("%PROJECT%", projectName_str);             // Set the PROJECT from our scratch projectName

    // Open the window
    window.open(moreURL_str, "_blank");
    return;
  }

  if (page_num == PAGE_PREV) {
    start_num -= 10;
  } else if (page_num == PAGE_NEXT) {
    start_num += 10;
  } else {
    start_num = (page_num - 1) * 10;
  }

  rebuildURL();
  init();
}

/**
 * Event handler for paging links when there are multiple projects in play.
 *
 * @param page_num  The page number or global flag for <code>PAGE_NEXT</code>,
 *                  <code>PAGE_NEXT</code>, or <code>PAGE_MORE</code>.
 */
function pagingCallbackMultiple(page_num) {
  if (page_num == PAGE_MORE) {
    // We may want to change this for multiple projects, but where to?
    // For right now the feature is that it will go the the primary project page
    moreURL_str = urlMaster_tmpl.replace("%SORT%", sortBy_str) // Set SORT from user pref
                                .replace("&colspec=%COLSPEC%", "")                // Remove COLSPEC - new page will have google defaults
                                .replace("%START%", 00)                           // We'll start at 0, 40 doesn't necessarily exist when dealing
                                                                                    //  with multiple projects
                                .replace("&num=%NUM%", "")                          // Don't limit the listing
                                .replace("%PROJECT%", projectName_str);             // Set the PROJECT from our scratch projectName

    // Open the window
    window.open(moreURL_str, "_blank");
    return;
  }

  if (page_num == PAGE_PREV) {
    currentPage_num--;
  } else if (page_num == PAGE_NEXT) {
    currentPage_num++;
  } else {
    currentPage_num = page_num;
  }

  inject();
}

/**
 * Adds functionality to the select box with an event handler.
 */
function addSelectEvent() {
  jQuery("#selectType").val(issueType_str);
  $("#selectType").change(function(event) {
    var searchType_str = jQuery(this).val();

    if (userName_str == ""  && (searchType_str == "assignedToMe" || searchType_str == "reportedByMe")) {
      // These search types require a user name.
      // We'll print a user name and die back to
      // the previous issue type.
      var needUserName_msg = new gadgets.MiniMessage(moduleID_num);
      needUserName_msg.createTimerMessage("To use this feature, please enter your user name in the preferences of this gadget.", 4);
      jQuery("#selectType").val(issueType_str);
    } else {
      issueType_str = searchType_str;
      prefs_obj.set("issueType", searchType_str);
      setFilteredSearch();

      localInit();
    }
  })
}

/**
 * Based on the current <code>issueType_str</code>, set <code>filterSearch_bool</code>,
 * and <code>filterSearch_str</code>.
 */
function setFilteredSearch() {
  switch(issueType_str) {
    case "all":
      filterSearch_bool = false;
      break;
    case "starred":
      filterSearch_bool = true;
      filterSearch_str = "&q=is%3Astarred";
      break;
    case "assignedToMe":
      filterSearch_str = "&q=owner%3A" + userName_str;
      filterSearch_bool = true;
      break;
    case "reportedByMe":
      filterSearch_str = "&q=reporter%3A" + userName_str;
      filterSearch_bool = true;
      break;
  }
}

/**
 * Used to create a new issue data object.
 *
 * @param sort_str        The item that will be sorted by.
 * @param id_str          The ID of the issue.
 * @param summary_str     The Summary of the issue.
 * @param projectName_str The project name of the issue.
 */
function issueData(sort_str, id_str, summary_str, projectName_str) {
  this.sort_str = sort_str;
  this.id_str = id_str;
  this.summary_str = summary_str;
  this.projectName_str = projectName_str;
}

/**
 * Helper function called by the sort method of the <code>issueData_arr</code>
 * array of issue data objects.
 *
 * @param a Issue data object.
 * @param b Issue data object.
 * @returns A <code>-1</code>, <code>1</code>, or <code>0</code> for <code>&lt;</code>,
 *          <code>&gt;</code>, or <code>==</code>
 */
function sortIssueData(a, b) {
  var a_str = a.sort_str.toLowerCase();
  var b_str = b.sort_str.toLowerCase();
  return ((a_str < b_str) ? -1 : ((a_str > b_str) ? 1 : 0));
}

/**
 * Helper function called by the sort method of the <code>issueData_arr</code>
 * array off issue data objects specifically when the sort by column is a date
 * of the type that Google Code employs: "XX hours ago", "XX days go", "MMM, YYYY".
 *
 * @param a Issue data object.
 * @param b Issue data object.
 * @returns A <code>-1</code>, <code>1</code>, or <code>0</code> for <code>&lt;</code>,
 *          <code>&gt;</code>, or <code>==</code>
 */
function sortIssueDataByDate(a, b) {
  var a_str = a.sort_str.toLowerCase();
  var b_str = b.sort_str.toLowerCase();

  // rateDate() will return a lower number for a more recent string type
  // moments < minutes < hours < days < MMM DD < MMM YYYY
  var aLevel_num = rateDate(a_str);
  var bLevel_num = rateDate(b_str);

  if (aLevel_num == bLevel_num) {
    // 'Moments Ago' are always equal
    if (aLevel_num == 0)
      return 0;

    // They have equal levels, get some more info and compare
    var a_num = a_str.match(/[0-9]+/);
    var b_num = b_str.match(/[0-9]+/);

    // Same value
    if (a_num == b_num)
      return 0;

    // a is greater, less recent unless "MMM DD" or "MMM YYYY" (rateDate > 3)
    if (Math.max(a_num, b_num) == a_num)
      return aLevel_num > 3 ? -1 : 1;

    // b is greater, less recent unless "MMM DD" or "MMM YYYY" (rateDate > 3)
    return aLevel_num > 3 ? 1 : -1;
  }

  if( Math.max(aLevel_num, bLevel_num) == aLevel_num ){
    // A is more recent
    return 1;
  }

  // B is more recent
  return -1;
}

/**
 * Helper function called by <code>sortIssueDataByDate</code> to rate the
 * different sorts of date strings that might be sent.  The lower the returned
 * number, the more recent
 *
 * Moments        0
 * Minutes        1
 * Hours          2
 * Days           3
 * This Year      4
 * Previous Year  5
 * Otherwise      6
 *
 * @param str The string to search for matching date information
 * @returns An integer that is lower for more recent strings.
 */
function rateDate(str) {
  if (str.search(/moments/) != -1)
    return 0;

  if (str.search(/minutes/) != -1)
    return 1;

  if (str.search(/hours/) != -1)
    return 2;

  if (str.search(/days/) != -1)
    return 3;

  if (str.search(/[a-z]{3}\ [0-9]{2}$/) != -1)
    return 4;

  if (str.search(/[a-z]{3}\ [0-9]{4}]/) != -1)
    return 5;

  return 6;
}

gadgets.util.registerOnLoadHandler(localInit);
